//  Filename must end with the suffix "_plugin.lua" .

PLUGIN.Name = "Physics Predictor (alpha)"
PLUGIN.DefaultOn = false
PLUGIN.Description = "Physics Predictor Alpha. Please do not alterate this file due to updates, or otherwise duplicate this file to make your own variant. This script makes use of \"Tolyzor\" research on the subject from the Wiremod forums."
PLUGIN.Trigger = false


function PLUGIN:LoadParameters()
	self:AddParameter("showphys",    { Type = "checkbox" , Text = "Show physics", Defaults = "1" } )
	self:AddParameter("showply",    { Type = "checkbox" , Text = "Show player line", Defaults = "0" } )
	--self:AddParameter("name_label", { Type = "label"    , Text = "Stuff name :" } )
	--self:AddParameter("name",       { Type = "textentry", Defaults = "Default contents." } )
	--self:AddParameter("max",        { Type = "slider"   , Text = "Maximum stuff allowed.", Defaults = "30", Min = "1", Max = "60", Decimals = "0" } )
	--self:AddParameter("doarrange",  { Type = "button"   , Text = "Rearrange Stuff now", DoClick = function() self:_Rearrange() end } )
	self:AddParameter("thickness",        { Type = "slider"   , Text = "Laser thickness", Defaults = "8", Min = "1", Max = "16", Decimals = "0" } )
end

function PLUGIN:Load()
	self.Mat = Material( "effects/yellowflare" )
	self.Color = Color( 119, 199, 255, 255 )
	
	self.Thickness = 8.0 -- Used as a cvar
	
	---- PhysicsCurve
	self.Precision = 24 -- This accounts point 0, so 14 would mean there are 15 points to calc.
	self.TimeLength = 10
	self.GravConst = 70    // TBD: Exact value based on server variables
	
	---- ComputeData
	self.EntAtt = nil
	self.LastTime = 0
	self.EnvData  = {}
	self.EntData  = {}
	self.PositionRecursion = Vector(0, 0, 0)
	self.VelocityRecursion = Vector(0, 0, 0)
	
	self:DefineServerEnv( self.EnvData )
	
end

function PLUGIN.HOOK:Think()
	if RealTime() < (self.LastTime + 0.12) then return end
	self.LastTime = RealTime()
	
	self.MyTraceData = utilx.GetPlayerTrace( LocalPlayer(), LocalPlayer():GetCursorAimVector() )
	self.MyTraceData.filter = {LocalPlayer() , LocalPlayer():GetVehicle()}
	self.MyTraceRes = util.TraceLine( self.MyTraceData )
	
	if (self.MyTraceRes.Hit) and (self.MyTraceRes.HitNonWorld) and ValidEntity(self.MyTraceRes.Entity) and (self.MyTraceRes.Entity ~= LocalPlayer()) then
		if self.EntAtt ~= self.MyTraceRes.Entity then
			self.EntAtt = self.MyTraceRes.Entity
			self:DefineEntitySpecs( self.EntData, self.EntAtt )
			
		end
		
	end
	
end

function PLUGIN:DefineEntitySpecs( tOut, ent )
	tOut.Ent = ent
	if not ValidEntity( ent ) then
		tOut.IsOriented = false
		tOut.OBBs = Vector(1, 1, 1)
		tOut.OBBs = Vector(1, 1, 1)
		tOut.LocalArea = nil
		--Theorical mass since its serverside
		tOut.Mass = 75
		
		return
		
	end
	
	tOut.IsOriented = true
	tOut.OBBs = ent:OBBMins() - ent:OBBMaxs()
	tOut.OBBs.x = math.abs( tOut.OBBs.x )
	tOut.OBBs.y = math.abs( tOut.OBBs.y )
	tOut.OBBs.z = math.abs( tOut.OBBs.z )
	
	tOut.Drag = Vector(1, 1, 1)
	
	tOut.LocalArea = Vector( tOut.OBBs.y * tOut.OBBs.z / tOut.Drag.x, tOut.OBBs.z * tOut.OBBs.x / tOut.Drag.y, tOut.OBBs.x * tOut.OBBs.y / tOut.Drag.z )
	--Theorical mass since its serverside
	tOut.Mass = 75
	
end

function PLUGIN:DefineServerEnv( tOut )
	tOut.SpeedLimit = GetConVarNumber( "sv_maxvelocity" ) or 4000
	tOut.AirDensity = GetConVarNumber( "sv_airdensity" )  or 2
	tOut.GravityVect  = Vector( 0, 0, -1 * ( GetConVarNumber( "sv_gravity" ) or 600 ) )
	tOut.UniversalDragCoeff = 8.18205e-6
	tOut.TickTime = 1 / 67
	
end

function PLUGIN:CalcEntityRecursion( tEnvData, tEntData, vPosition, vVelocity, vPositionOut, vVelocityOut )
	local area = tEntData.Ent:WorldToLocal( tEntData.Ent:GetPos() + vVelocity ):Normalize():Dot( tEntData.LocalArea )
	area = math.abs( area )
	
	local drag = -1 * tEnvData.UniversalDragCoeff * area * tEnvData.AirDensity * vVelocity:Length()^2 * vVelocity:Normalize()
	local accel = tEnvData.GravityVect + drag / tEntData.Mass
	vVelocityOut = vVelocity + accel * tEnvData.TickTime
    if vVelocityOut:Length() > tEnvData.SpeedLimit then
		vVelocityOut = vVelocityOut:Normalized() * tEnvData.SpeedLimit
		
	end
	
    vPositionOut = vPosition + 0.5 * (vVelocity + vVelocityOut) * tEnvData.TickTime
    
end
/*
function PLUGIN.HOOK:PostDrawTranslucentRenderables()
	self.Thickness = self:GetNumber( "thickness" )
	render.SetMaterial( self.Mat )
	
	local __showphysics = self:GetNumber("showphys") > 0
	for k,ply in pairs( player.GetAll() ) do
		if ValidEntity( ply ) then
			if not ply.__communitycolor then
				local h   = ply:UserID()^5 % 360
				local s   = 0.1 + 0.5 * (ply:UserID()^3 % 27) / 27
				local v   = 1.0
				ply.__communitycolor = HSVToColor(h, s, v)
			end
		
			if ply ~= LocalPlayer() or self:GetNumber("showply") > 0 then
				// not optimized
				render.DrawBeam( ply:GetShootPos() + ply:EyeAngles():Right() * 0, 		
								 ply:GetShootPos() + ply:EyeAngles():Forward() * 4096,
								 self.Thickness,					
								 0.5,
								 0,
								 ply.__communitycolor or self.Color )
			end
				
			if __showphysics then
				// TBD: Exact value based on server variables
				self:RenderPhysicsCurve( 350, ply:EyeAngles(), ply:GetShootPos(), ply.__communitycolor or self.Color )
				
			end
		
		end
		
	end
	
	if ValidEntity( self.EntAtt ) then
		local fPickDelay = self.TimeLength / self.Precision
		local iPickInterval = fPickDelay / self.EnvData.TickTime
		local iAttempts = iPickInterval * self.Precision
		local cColor = self.Color
		
		render.StartBeam( self.Precision + 1 )
		
		--pcall(function()
			render.AddBeam( self.PositionRecursion, self.Thickness, 0.5, cColor )
			for i = 1, iAttempts do
				local rel = i / iAttempts
				self.PositionRecursion = self.EntAtt:LocalToWorld( self.EntAtt:OBBCenter() )
				self.VelocityRecursion = self.EntAtt:GetVelocity()
				self:CalcEntityRecursion( self.EnvData, self.EntData, self.PositionRecursion, self.VelocityRecursion, self.PositionRecursion, self.VelocityRecursion )
				
				render.AddBeam( self.PositionRecursion, self.Thickness, 0.5 - 0.4 * rel, cColor )
			end
			
		--end)
		
		render.EndBeam( )
		
	end
	
end
*/

-- CURVE MODE
function PLUGIN.HOOK:PostDrawTranslucentRenderables()
	self.Thickness = self:GetNumber( "thickness" )

	--if not LocalPlayer or not ValidEntity( LocalPlayer() ) then return end

	render.SetMaterial( self.Mat )
	
	local __showphysics = self:GetNumber("showphys") > 0
	for k,ply in pairs( player.GetAll() ) do
		if ValidEntity( ply ) then
			if not ply.__communitycolor then
				local h   = ply:UserID()^5 % 360
				local s   = 0.1 + 0.5 * (ply:UserID()^3 % 27) / 27
				local v   = 1.0
				ply.__communitycolor = HSVToColor(h, s, v)
			end
		
			if ply ~= LocalPlayer() or self:GetNumber("showply") > 0 then
				// not optimized
				render.DrawBeam( ply:GetShootPos() + ply:EyeAngles():Right() * 0, 		
								 ply:GetShootPos() + ply:EyeAngles():Forward() * 4096,
								 self.Thickness,					
								 0.5,
								 0,
								 ply.__communitycolor or self.Color )
			end
				
			if __showphysics then
				// TBD: Exact value based on server variables
				self:RenderPhysicsCurve( 350, ply:EyeAngles(), ply:GetShootPos(), ply.__communitycolor or self.Color )
				
			end
		
		end
		
	end
	
	if ValidEntity( self.EntAtt ) then
		self:RenderPhysicsCurve( self.EntAtt:GetVelocity():Length() * 0.2, self.EntAtt:GetVelocity():Angle(), self.EntAtt:LocalToWorld( self.EntAtt:OBBCenter() ), self.Color )
		
	end
	
end

function PLUGIN:RenderPhysicsCurve( fInitialSpeed, aDirection, vPos, cColor )

	render.StartBeam( self.Precision + 1 )

	pcall(function()
	
	self._forwardNorm = Angle( 0, aDirection.y, 0 ):Forward()
	
	local position = Vector(0, 0, 0)
	local time = 0.0
	local rel = 0.0
	local x_shift = 0.0
	local y_shift = 0.0
	render.AddBeam( vPos, self.Thickness, 0, cColor )
	for i = 1, self.Precision do
		rel = i / self.Precision
		time = self.TimeLength * rel
		
		x_shift = fInitialSpeed * math.cos( math.rad( -aDirection.p ) ) * time
		y_shift = -0.5 * self.GravConst * time^2 + fInitialSpeed * math.sin( math.rad( -aDirection.p ) ) * time
		
		position.x = vPos.x
		position.y = vPos.y
		position.z = vPos.z + y_shift
		position = position + self._forwardNorm * x_shift
		
		render.AddBeam( position, self.Thickness, 0.5 - 0.4 * rel, cColor )
		
	end
	
	end)
	
	render.EndBeam()

end

function PLUGIN:Unload()
end

